#include "SimpleSAPI.h"

// Code based on tutorial at 
// http://msdn.microsoft.com/en-us/library/ms720163(VS.85).aspx

#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override something,
//but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

#include <sapi.h>
#include <sphelper.h>

// Not in class to avoid having to include template header in header
CComPtr<ISpVoice>		m_cpVoice;			// The TTS voice
CComPtr<ISpRecoContext>	m_cpRecoContext;	// The SR context
CComPtr<ISpRecoGrammar>	m_cpGrammar;		// The SR grammar

//------------------------------------------------------------------
// SimpleSAPI							
//------------------------------------------------------------------
SimpleSAPI::SimpleSAPI()
	: m_pCallback(NULL)
{
	// Don't auto-init, let client supply a language ID
}

//------------------------------------------------------------------
// ~SimpleSAPI							
//------------------------------------------------------------------
SimpleSAPI::~SimpleSAPI()
{
	// Static deallocation usually happens at program end
	Shutdown();
}

//------------------------------------------------------------------
// GetInstance					
//------------------------------------------------------------------
SimpleSAPI& SimpleSAPI::GetInstance()
{
	static SimpleSAPI inst;
	return inst;
}

//------------------------------------------------------------------
// Init			
//------------------------------------------------------------------
void SimpleSAPI::Init(LanguageID srLanguage)
{
	// Initialize COM
	if (!FAILED(::CoInitialize(NULL)))
	{
		// Create a voice for TTS synthesis
		m_cpVoice.CoCreateInstance(CLSID_SpVoice);

		// Create a recognition context for recognition
		HRESULT hr = m_cpRecoContext.CoCreateInstance(CLSID_SpSharedRecoContext);
		if (SUCCEEDED(hr))
		{
			// Tell it that we only care about speech recognition events
			hr = m_cpRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));

			// Create a SR grammar
			hr = m_cpRecoContext->CreateGrammar(0, &m_cpGrammar.p);
			if (SUCCEEDED(hr))
			{
				// Just use a blank grammar for now
				m_cpGrammar->ResetGrammar(srLanguage);

				//// Dictation mode
				//m_cpGrammar->LoadDictation(NULL, SPLO_STATIC);
				//m_cpGrammar->SetDictationState(SPRS_ACTIVE);

				//// Command & control mode from a file
				//hr = m_cpGrammar->LoadCmdFromFile(pGrammarFilename, SPLO_DYNAMIC);
				//if ( SUCCEEDED( hr ) )
				//{
				//	// Activate all the rules
				//	for (unsigned rule = 1; rule <= numRulesInGrammar; ++rule)
				//	{
				//		m_cpGrammar->SetRuleIdState(rule, SPRS_ACTIVE);
				//	}
				//}
			}
		}
	}
}

//------------------------------------------------------------------
// Shutdown									
//------------------------------------------------------------------
void SimpleSAPI::Shutdown()
{
	m_cpVoice.Release();
	m_cpGrammar.Release();
	m_cpRecoContext.Release();

	// Uninit COM
	::CoUninitialize();
}

//------------------------------------------------------------------
// Update		
//------------------------------------------------------------------
void SimpleSAPI::Update()
{
	if (!m_cpRecoContext.p)
		return;

	// Get all events - note that GetFrom returns S_FALSE when it
	// has no events to get, which passes the SUCCEEDED macro test.
	// We therefore specifically check for S_OK.
	CSpEvent evt;
	while (S_OK == evt.GetFrom(m_cpRecoContext))
	{
		if (evt.eEventId == SPEI_RECOGNITION &&
			evt.RecoResult())
		{
			HRESULT hr = S_OK;
			WCHAR*	pwszText = NULL;
			ISpRecoResult* pResult = evt.RecoResult();

			// Get the phrase's entire text string, including replacements.
			hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &pwszText, NULL);
			if (SUCCEEDED(hr))
			{
				// Activate callback
				OnRecognized(std::wstring(pwszText));
				CoTaskMemFree(pwszText);
			}
		}
	}
}

//------------------------------------------------------------------
// OnRecognized			
//------------------------------------------------------------------
void SimpleSAPI::OnRecognized(const std::wstring& text)
{
	if (!m_pCallback)
		return;

	// Find the rule ID
	unsigned ruleID = 0;
	for (SRList::iterator it = m_SpeechRules.begin();
		it != m_SpeechRules.end(); ++it)
	{
		if (it->second == text)
		{
			ruleID = it->first;
			break;
		}
	}

	// Callback
	m_pCallback(ruleID, text);
}

//------------------------------------------------------------------
// Speak	
//------------------------------------------------------------------
void SimpleSAPI::Speak(const std::wstring& text, bool bUseXML, bool bAsync, bool bPurgeQueue)
{
	Speak(text.c_str(), bUseXML, bAsync, bPurgeQueue);
}

//------------------------------------------------------------------
// Speak	
//------------------------------------------------------------------
void SimpleSAPI::Speak(const std::string& text, bool bUseXML, bool bAsync, bool bPurgeQueue)
{
	// Convert to wstring
	std::wstring wstr;
	for (std::string::const_iterator it = text.begin();
		it != text.end(); ++it)
	{
		wstr.push_back((wchar_t)(*it));
	}

	Speak(wstr, bUseXML, bAsync, bPurgeQueue);
}

//------------------------------------------------------------------
// Speak	
//------------------------------------------------------------------
void SimpleSAPI::Speak(const wchar_t* pText, bool bUseXML, bool bAsync, bool bPurgeQueue)
{
	if (m_cpVoice.p)
	{
		DWORD flags = bUseXML ? SPF_IS_XML : SPF_IS_NOT_XML;
		if (bAsync)
			flags |= SPF_ASYNC;

		if (bPurgeQueue)
			flags |= SPF_PURGEBEFORESPEAK;

		m_cpVoice->Speak(pText, flags, NULL);
	}
}

//------------------------------------------------------------------
// TestVoices
//------------------------------------------------------------------
void SimpleSAPI::TestVoices()
{
	if (!m_cpVoice)
		return;

	// Declare local identifiers:
	HRESULT                        hr = S_OK;
	CComPtr<ISpObjectToken>        cpVoiceToken;
	CComPtr<IEnumSpObjectTokens>   cpEnum;
	ULONG                          ulCount = 0;
	int							   i = 0;

	// Enumerate the available voices.
	hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum);
	if (SUCCEEDED(hr))
	{
		// Get the number of voices.
		hr = cpEnum->GetCount(&ulCount);
	}

	// Obtain a list of available voice tokens, set
	// the voice to the token, and call Speak.
	while (SUCCEEDED(hr) && ulCount--)
	{
		cpVoiceToken.Release();

		if (SUCCEEDED(hr))
		{
			hr = cpEnum->Next(1, &cpVoiceToken, NULL);
		}

		if (SUCCEEDED(hr))
		{
			hr = m_cpVoice->SetVoice(cpVoiceToken);
		}

		WCHAR buf[128];
#pragma warning (disable:4995)
		wsprintf(buf, L"Testing system voice number %d", i++);
#pragma warning (default:4995)

		Speak(buf, false, false, false);
	}
}

//------------------------------------------------------------------
// SetVoice
//------------------------------------------------------------------
void SimpleSAPI::SetVoice(unsigned idx)
{
	if (!m_cpVoice)
		return;

	// Declare local identifiers:
	HRESULT                        hr = S_OK;
	CComPtr<ISpObjectToken>        cpVoiceToken;
	CComPtr<IEnumSpObjectTokens>   cpEnum;
	ULONG                          ulCount = 0;
	unsigned					   i = 0;

	// Enumerate the available voices.
	hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum);
	if (SUCCEEDED(hr))
	{
		// Get the voice token
		hr = cpEnum->Item(idx, &cpVoiceToken);

		if (SUCCEEDED(hr))
		{
			hr = m_cpVoice->SetVoice(cpVoiceToken);
		}
	}
}

//------------------------------------------------------------------
// AddCommand						
//------------------------------------------------------------------
int SimpleSAPI::AddCommand(const std::wstring& command)
{
	if (!m_cpGrammar.p)
		return -1;

	// Unique rule ID
	static int nextRule = 0;
	++nextRule;

	// Add a dynamic rule
	HRESULT hr = S_OK;
	SPSTATEHANDLE pState = 0;
	hr = m_cpGrammar->GetRule(0, nextRule, SPRAF_TopLevel | SPRAF_Dynamic, true, &pState);
	if (SUCCEEDED(hr))
	{
		hr = m_cpGrammar->AddWordTransition(pState, 0, command.c_str(), 0, SPWT_LEXICAL, 1.0f, 0);
		if (SUCCEEDED(hr))
		{
			hr = m_cpGrammar->Commit(0);
			if (SUCCEEDED(hr))
			{
				hr = m_cpGrammar->SetRuleIdState(nextRule, SPRS_ACTIVE);

				// Add a record of it
				m_SpeechRules.push_back(std::make_pair((unsigned)nextRule, command));

				return nextRule;
			}
		}
	}

	return -1;
}


